package net.scala.chapter5.test

import java.io.File

import com.typesafe.scalalogging.slf4j.LazyLogging
import org.scalatest._

import scala.annotation.tailrec
import scala.io.Source

/**
 * @author Barudisshu
 */
class TestScala extends FunSuite with BeforeAndAfter with Matchers with LazyLogging {


  info("Starting... ")

  /**
   * 函数式编程是由lambda演算得来的
   * λ演算函数是第一类函数
   * 从数学角度讲：
   * 函数既是存在x，在定义域D上，x∈D，使得总有唯一的f(x)与之对应
   */
  test("What is functional programming?"){

    // lambda演算都是匿名的，纯函数的输出结果总是相同的（指的是结果类型和形式一致）
    def add(a: Int, b: Int): Int = a + b
    println(add(10, 10))

    // 内部状态和行为发生改变的函数不是纯函数
    def weather(zipCode: String) = {
      val url =
        "http://api.wunderground.com/auto/wui/geo/GeoLookupXML/index.xml?query="
      scala.io.Source.fromURL(url + zipCode)
    }

    // 纯函数要求是引用透明的实现
    println(add(10, 10) + add(5, 5))
  }

  /**
   * 引用透明带来的好处
   */
  test("The benefits of referential transparency"){
    /*
    尽管其他编程语言也可以实现函数式编程，但是确实难于实现以及难于使用的。因为他们难于实现引用透明。
    在Scala中，方法(method)和函数(function)的区别是：
    方法：没有返回类型，或者只返回闭包的类型
    函数：有返回类型，由类型和对象表示
     */
  }

  /**
   * 纯函数编程
   * 一个纯函数编程就是一个单引用透明表达式(single referentially transparent expression)
   * 因此，非纯函数或不是引用透明的表达式是带副作用的(side effects)
   */
  test("A pure functional program"){
    object PureFunctionalProgram{
      def main(args: Array[String]):Unit = singleExpression(args.toList)
      def singleExpression: List[String] => (List[Int], List[Int]) = { a =>
        a map (_.toInt) partition (_ < 30)
      }
    }
    val array = Array("10","20","30","40")
    // main 作为纯函数的入口
    PureFunctionalProgram main array
    // 引用透明：函数的行为不会发生改变
    println(array.toList)
  }

  /**
   * Scala同时支持实现FP(Functional Program)和OOP(Object-Oriental Program)，
   * OOP属于支撑和辅助支持，在编写代码中应该过渡并尽量使用FP
   */
  test("Moving from OOP to functional programming"){}

  /**
   * 纯函数与非纯函数
   */
  test("Pure vs. impure programing"){

    // 一个非纯的实现
    // 因为area的值依赖于外部因素side，因此需要时刻关注外部状态的改变
    class Square(var side:Int){
      def area = side * side
    }

    // 使用Successor Value Pattern(也叫函数对象模式，Functional Pattern)
    // 返回改变自身状态后的一个拷贝
    class PureSquare(val side:Int){
      def newSide(s:Int) = new PureSquare(s)
      def area = side * side
    }

    println(new PureSquare(4).area)

  }

  /**
   * 面向对象的设计模式在FP中依然很有用，并且更容易实现
   * FP中带来了更多设计模式，并且主要实现与递归相关
   */
  test("Object-oriented patterns in functional programming"){

    trait TaxStrategy {def taxIt(product: String): Double }
    class ATaxStrategy extends TaxStrategy {
      def taxIt(product: String): Double = 1
    }
    class BTaxStrategy extends TaxStrategy {
      def taxIt(product: String): Double = 2
    }
    def taxIt: TaxStrategy => String => Double = s => p => s.taxIt(p)
  }

  /**
   * 实际开发不可避免会有side effects，最好的做法就是将pure内容分离出来，也就是设计上的分层
   */
  test("Modeling purely functional programs"){

    /*
     * 模拟实现HTTP获取文件请求
     */
    // 1. 抽象纯实现
    object Pure {
      trait Resource {
        def exists: Boolean
        def contents: List[String]
        def contentLength: Int
      }
      type ResourceLocator = String => Resource
      type Request = Iterator[Char]
      type Response = List[String]

      // Parse HTTP request for filename
      def get(req: Request)(implicit locator: ResourceLocator): Response = {
        val requestedResponse = req.takeWhile(x => x != '\n').mkString.split(" ")(1).drop(1)
        (_200 orElse _404)(locator(requestedResponse))
      }

      //
      private def _200: PartialFunction[Resource, Response] = {
        case resource if resource.exists =>
            "HTTP/1.1 200 OK" ::
            ("Date " + new java.util.Date) ::
            "Content-Type: text/html" ::
            ("Content-Length: " + resource.contentLength) ::
            System.getProperty("line.separator") ::
            resource.contents
      }
      private def _404: PartialFunction[Resource, Response] = {
        case _ => List("HTTP/1.1 404 Not Found")
      }
    }

    // 2. 实现不纯的层(layers)，从Pure functional中分离出side effects
    import Pure._
    case class IOResource(name: String) extends Resource {
      def exists = new File(name).exists
      def contents = Source.fromFile(name).getLines().toList
      def contentLength = Source.fromFile(name).count(x => true)
    }
    implicit val ioResourceLocator: ResourceLocator = name => IOResource(name)
  }

  test("Functions in all shapes and forms"){}

  /**
   * 方法与函数的区别在于，
   * 函数是通过类型和对象表示的，即函数有类型返回，但是方法不是
   * 方法通常被定义在一个封闭类的内部，因此要小心封闭类内部的状态，因为有可能导致状态的改变
   * 在Scala中，要实现方法-函数的转换，通常使用一个Eta扩展实现，即在方法后面加 _ 下划线
   */
  test("Methods vs. functions"){
    // 方法通常被定义在一个封闭类的内部
    class UseResource {
      def use(r: String): Boolean = r.isEmpty
    }
    // 函数字面量(匿名函数)
    val succ = (x: Int) => x + 1
    // 等价于，其中FunctionX可以用语法糖代替
    val succFunction = new Function1[Int, Int] {
      def apply(x: Int): Int = x + 1
    }

    // 使用Eta 表达式实现方法到函数的转换
    val use_func: String => Boolean = (new UseResource).use _

    println(use_func(""))
  }

  /**
   * 高阶函数设计思想
   * 高阶函数：指将函数作为参数或者函数作为返回值的函数
   */
  test("Higher-order functions") {
    // 高阶函数在Scala集合中飞航普遍
    val l = List(1, 3, 5, 7, 10, 15)
    l.filter(_ % 2 == 0)
    println(l)

    // 通常情况下，高阶函数用于实现公共的、常用的、通用的功能实现
    trait Resource {
      def dispose()
    } // Resource在函数式编程设计上称为借贷模式(Loan Pattern)，
    // 在OOP中则称为模版方法(Template Method pattern)
    def use[A, B <: Resource](r: Resource)(f: Resource => A): A = {
      try {
        f(r)
      } finally {
        r.dispose()
      }
    }

    // 高阶函数通常可用于实现数据的结构化，并可提取可重用代码
    object Combinators {
      implicit def kestrel[A](a: A) = new {
        def tap(sideEffect: A => Unit): A = {
          sideEffect(a)
          a
        }
      }
    }
    case class Person(firstName: String, lastName: String)
    case class Mailer(mailAddress: String) {
      def mail(body: String) = {
        println("send mail here...")
      }
    }
    import Combinators._
    Person("Nilanjan", "Raychaudhuri").tap(p =>{
      println("First name " + p.firstName)
      Mailer("some address")
    }).lastName
    // 这种函数的组合实现称之为Kestrel，参考[书]To Mock a Mockingbird

  }

  /**
   * 函数科里化
   * 科里化用于将多参数的函数转换为一个参数的函数
   */
  test("Function currying"){
    trait TaxStrategy {def taxIt(product: String): Double}
    class TaxFree extends TaxStrategy {
      override def taxIt(product: String) = 0.0
    }
    val taxIt1: (TaxStrategy, String) => Double = (s, p) => s.taxIt(p)
    def taxIt2(s: TaxStrategy, product: String) = { s.taxIt(product) }    // 1. 通过curried方法实现
    val c1 = taxIt1.curried
    // 2. 通过占位符实现
    val c2 = (taxIt2 _).curried
    // 3. 直接编写科里化风格
    def taxIt3(s: TaxStrategy)(product: String) = {
      s.taxIt(product)
    }
    val c3 = taxIt3(new TaxFree) _
    assert(c1(new TaxFree)("") == c2(new TaxFree)(""))
    assert(c1(new TaxFree)("") == c3(""))
  }

  /**
   * 组合函数和偏函数
   * 偏函数是指：一个函数仅仅为输入值子串定义。
   * 偏函数的数学定义为，f: X->Y，表示仅定义了X=1和X=3，不包含X=2。
   */
  test("Function composition and partial functions"){

    // 偏函数的最简单实现形式是使用模式匹配
    def intToChar:PartialFunction[Int,Char] = {
      case 1 => 'a'
      case 3 => 'c'
    }
    // Scala的编译器会将上面的代码转换为如下等效实现
    new PartialFunction[Int,Char] {
      def apply(i:Int) = i match {
        case 1 => 'a'
        case 3 => 'c'
      }
      def isDefinedAt(i:Int):Boolean = i match{     // isDefinedAt决定了是否要为给定的参数赋予偏函数操作处理
        case 1 => true
        case 3 => true
        case _ => false
      }
    }

    // 实现偏函数的组合可以解决复杂的业务逻辑问题。例如一个发票索赔解决方案
    sealed trait Claim {val claimId: Int}
    case class Full(claimId: Int) extends Claim
    case class Partial(claimId: Int, percentage: Double) extends Claim
    case class Generic(override val claimId: Int) extends Claim

    case class Location(stateCode: Option[String], zipCode: Option[String])
    case class Req(productId: String, location: Location, claim: Claim)

    object PricingSystem {
      type PC = Tuple2[Req, Option[Double]]
      def handleFullClaim: PartialFunction[PC, PC] = {
        case (c@Req(id, l, Full(claimId)), basePrice) => (c, basePrice.map(_ + 10))
      }

      def handlePartialClaim: PartialFunction[PC, PC] = {
        case (c@Req(id, l, Partial(claimId, percentage)), basePrice) => (c, basePrice.map(_ + 20))
      }

      def handleZipCode: PartialFunction[PC, PC] = {
        case (c@Req(id, Location(_, Some(zipCode)), _), price) => (c, price.map(_ + 5))
      }

      def handleStateCode: PartialFunction[PC, PC] = {
        case (c@Req(id, Location(Some(stateCode), _), _), price) => (c, price.map(_ + 10))
      }

      def claimHandlers = handleFullClaim orElse handlePartialClaim
      def locationHandlers = handleZipCode orElse handleStateCode

      def default: PartialFunction[PC, PC] = {
        case p => p
      }
      def priceCalculator: PartialFunction[PC, PC] =
        (claimHandlers andThen locationHandlers) orElse default

      def main(args: Array[String]) = {
        priceCalculator((Req("some product", Location(None, Some("43230")), Full(1)), Some(10))) match {
          case (c, finalPrice) => println(finalPrice)
        }
        priceCalculator((Req("some product", Location(None, None), Generic(10)), Some(10))) match {
          case (c, finalPrice) => println(finalPrice)
        }
      }
    }
  }

  /**
   * 递归实现
   */
  test("Recursion"){
    var s = 0; for(e <- List(1,2,3)){s += e}
    // 递归实现
    def sum(xs:List[Int]):Int = xs match {
      case Nil => 0
      case x::ys => x + sum(ys)
    }
  }

  /**
   * 递归地思考，递归实现的主要步骤有
   * 1. 定义方法名和方法返回类型
   * 2. 根据约束条件，实现CASE用例模式匹配
   * 3. 实现可迭代CASE模块
   */
  test("Thinking recursively"){
    // 创建一个去重复的方法
    def removeDups[A](xs:List[A]):List[A] = xs match{
      case Nil => Nil
      case x::ys if ys contains x => removeDups(ys)
      case x::ys => removeDups(ys) :+ x
    }
    val list = List(0, 1, 2, 3, 2, 1, 0)
    println(removeDups(list))
  }

  /**
   * 尾递归
   * 从执行顺序上看，尾递归的性能比较好，实现了尾部调用优化的尾递归会被编译器转换
   * 为loop循环执行，因此不用担心会发生空间不够而抛出的堆栈异常问题
   */
  test("tail recursion"){
    // 头递归(head recursion):递归操作发生在其他操作的前面，即先迭代，再处理
    def length[A](xs: List[A]): Int = xs match {
      case Nil => 0
      case x :: ys => 1 + length(ys)
    }
    // 尾递归(tail recursion):递归操作发生在处理之后
    def length2[A](xs: List[A]): Int = {
      @tailrec
      def _length(xs: List[A], currentLength: Int): Int = xs match {
        case Nil => currentLength
        case x :: ys => _length(ys, currentLength + 1)
      }
      _length(xs, 0)
    }

    // 从Scala2.8开始，编译器实现了尾部调用优化来解决递归的堆栈问题，但是要确定
    // 该优化是否处理了递归操作。因此，要确定是否发生了调用优化处理，可以在方法前
    // 添加@tailrec来判断，如果不能优化，将抛出警告
    val list = List(1,2,3,4,5,6,7)
    println(length2(list))
  }

  /**
   * 代数数据类型(Algebraic data type,ADT)
   * ADT通常是指值的集合，如Int数据类型就是指所有整形值的集合
   * 你可以通过两种方式实现ADT，
   * 1. 通过枚举进行定义
   * 2. 使用模式匹配对case class进行解组
   * 实际上，一般推荐使用case class+模式匹配，因为模式匹配发生在代码外部，由编译器处理，另外就是可以方便实现全函数
   * 若对ADT概念不够理解，可以参考scala.Either 和 scala.Option
   */
  test("Algebraic data types"){

    object ADT{
      sealed trait Account
      case class CheckingAccount(accountId:String) extends Account
      case class SavingAccount(accountId:String,limit:Double) extends Account
      case class PremiumAccount(corporateId: String, accountHolder: String) extends Account

      // 使用模式匹配对实现ADT的case class结构类型进行解组
      def printAccountDetails(account:Account):Unit = account match {
        case CheckingAccount(accountId) => println("Account id " + accountId)
        case SavingAccount(accountId,limit) => println("Account id " + accountId + " , " + limit)
      }
      // 注意：如果Account的模式匹配不全，Scala编译器将提示如下missing告警
      // [warn] missing combination PremiumAccount
      // [warn] def printAccountDetails(account: Account): Unit = account match {
      // 告警说明漏掉了PremiumAccount，处理掉告警的方法是:
      // 1. 补上PremiumAccount的case；2. 去掉Account的sealed关键字

      val premiumAccount = PremiumAccount("2234fal","9s9kaZ9f")
      printAccountDetails(premiumAccount)   // 因为函数匹配不全，编译器将提示告警

      // --由于代码处于单元测试块，所以暂时不考虑sealed作用范围问题--
    }
  }

  /**
   * 函数式编程为什么重要?
   * 和类Unix的管道技术一样，Scala提供了 andThen 和 compose 方法对函数实现组合
   * 之所以进行组合的设计原则是：
   * 1. 大规模和复杂开发，需要为其提供合适的解决方法，而最传统的方法就是对问题进行
   * 分离，对应Scala就是将问题以功能的形式分解成一个个的纯函数；分离的问题再汇总实
   * 现组合(composability)，最终得到问题的全解。因此，从数学角度看，实际上是构建
   * 了函数的微观世界(mathematical microworlds)，如何在分离中进行抽象(abstraction)，
   * 则是解决这个微观世界的熵问题，以使得组合和分离更快速。
   * 2. 调试。因为纯函数不会有副作用问题，分离后只需要对参数驱动的函数(惰性执行)进行操作。
   * 3. 更少的代码改造，更多的代码复用，以及更容易维护。因此从熵信息的孩子节点使该使函数
   * 的参数是指定的(specific)，而使父节点函数的参数是通用的(generic)。
   */
  test("Why does functional programming matter?"){
    // 假设要获取集合中的偶数，并乘2，给定的函数有
    def even: Int => Boolean = _ % 2 == 0
    def not: Boolean => Boolean = !_
    def filter[A](criteria: A => Boolean)(col: Traversable[A]) =
      col.filter(criteria)
    def map[A, B](f: A => B)(col: Traversable[A]) = col.map(f)

    // 通过科里化定义偶数过滤
    def evenFilter = filter(even) _
    def double: Int => Int = _ * 2

    // 实现组合(composability)，通过Scala提供的方法 andThen 实现管道技术
    def doubleAllEven = evenFilter andThen map(double)

    println(doubleAllEven(List(1,2,3,4)))

    // 通过Scala提供的方法 compose 实现管道技术
    // compose 和 andThen 功能一样，只不过 compose 的执行顺序是从右往左
    def odd: Int => Boolean = not compose even
    def oddFilter = filter(odd) _
    def doubleAllOdd = oddFilter andThen map(double)

    println(doubleAllOdd(List(1,2,3,4)))

  }

  /*==============================================================================
                                  State Monad
  ==============================================================================*/
  // Specify using monad instead fo pile invoke
  object StateMonad {

    import State._

    trait State[S, +A] {
      def apply(s: S): (S, A)
      def map[B](f: A => B): State[S, B] = state(f = apply(_) match {
        case (s, q) => (s, f(q))
      })
      def flatMap[B](f: A => State[S, B]): State[S, B] =
        state(apply(_) match {
          case (s, q) => f(q)(s)
        })
    }
    object State {
      def state[S, A](f: S => (S, A)) = new State[S, A] {
        def apply(s: S) = f(s)
      }
      def init[S]: State[S, S] = state[S, S](s => (s, s))
      def modify[S](f: S => S) =
        init[S] flatMap (s => state(_ => (f(s), ())))

      // use for logging
      def gets[S,A](f: S => A): State[S, A] =
        init[S] flatMap (s => state(_ => (s, f(s))))
    }
  }

  object Stubs {
    def findTheBasePrice(productId: String) = 10.0
    def findStateSpecificDiscount(productId: String, stateCode: String) = 0.5
    def findProductSpecificDiscount(productId: String) = 0.5
    def calculateTax(productId: String, price: Double) = 5.0
  }

  /*==============================================================================
                                  Monad End
  ==============================================================================*/

  /**
   * 要实现第一类monad，你必须实现map 和 flatMap这两个方法，
   * 因为for-comprehension不是别的而是map/flatMap的语法糖，
   * 而使用monad正是使用了for-comprehension作为实现。
   */
  test("Managing state using monads"){

    object PriceCalculatorWithoutMonad {
      import Stubs._
      case class PriceState(productId:String,stateCode:String,price:Double)
      def findBasePrice(productId:String,stateCode:String):PriceState = {
        val basePrice = findTheBasePrice(productId)
        PriceState(productId,stateCode,basePrice)
      }
      def applyStateSpecificDiscount(ps:PriceState):PriceState = {
        val discount = findStateSpecificDiscount(ps.productId,ps.stateCode)
        ps.copy(price = ps.price - discount)
      }
      def applyProductSpecificDiscount(ps:PriceState):PriceState = {
        val discount = findProductSpecificDiscount(ps.productId)
        ps.copy(price = ps.price - discount)
      }
      def applyTax(ps:PriceState):PriceState = {
        val tax = calculateTax(ps.productId,ps.price)
        ps.copy(price = ps.price + tax)
      }
      def calculatePrice(productId:String,stateCode:String):Double = {
        val a = findBasePrice(productId,stateCode)
        val b = applyStateSpecificDiscount(a)
        val c = applyProductSpecificDiscount(b)
        val d = applyTax(c)
        d.price
      }
    }

    //
    println(PriceCalculatorWithoutMonad.calculatePrice("xxx","iii"))

    // 一系列方法调用序列，通过for-comprehension放置，这样带来两点好处
    // 1. 确保方法被执行
    // 2. 被调用的方法彼此独立，不相互影响
    import StateMonad.State._

    object PriceCalculatorWithMonad {

      import Stubs._
      case class PriceState(productId: String, stateCode: String, price: Double)
      def findBasePrice(ps: PriceState): Double = {
        findTheBasePrice(ps.productId)
      }
      def applyStateSpecificDiscount(ps: PriceState): Double = {
        findStateSpecificDiscount(ps.productId, ps.stateCode)
      }
      def applyProductSpecificDiscount(ps: PriceState): Double = {
        ps.price - findProductSpecificDiscount(ps.productId)
      }
      def applyTax(ps: PriceState): Double = {
        ps.price + calculateTax(ps.productId, ps.price)
      }

      def calculatePrice(productId: String, stateCode: String): Double = {
        def modifyPriceState(f: PriceState => Double) = modify[PriceState](s => s.copy(price = f(s)))
        val stateMonad = for {
          _ <- modifyPriceState(findBasePrice)
          _ <- modifyPriceState(applyStateSpecificDiscount)
          _ <- modifyPriceState(applyProductSpecificDiscount)
          _ <- modifyPriceState(applyTax)
        } yield ()
        val initialPriceState = PriceState(productId, stateCode, 0.0)
        val finalPriceState = stateMonad.apply(initialPriceState)._1
        val finalPrice = finalPriceState.price
        finalPrice
      }

      // 为每个步骤使用Log，每步执行完打印日志
      def calculatePriceWithLog(productId: String, stateCode: String): Double = {
        def modifyPriceStateWithLog(f: PriceState => Double) = modify[PriceState](s => s.copy(price = f(s)))
        def logStep(f: PriceState => String) = gets(f)
        val stateMonad = for {
          _ <- modifyPriceStateWithLog(findBasePrice)
          a <- logStep(s => "Base Price " + s)
          _ <- modifyPriceStateWithLog(applyStateSpecificDiscount)
          b <- logStep(s => "After state discount " + s)
          _ <- modifyPriceStateWithLog(applyProductSpecificDiscount)
          c <- logStep(s => "After product discount " + s)
          _ <- modifyPriceStateWithLog(applyTax)
          d <- logStep(s => "After tax " + s)
        } yield a :: b :: c :: d :: Nil
        val (finalPriceState, log) = stateMonad.apply(PriceState(productId,
          stateCode, 0.0))
        println(">>>>>>>>> " + finalPriceState.price)
        println("log " + log)
        finalPriceState.price
      }
    }

    println(PriceCalculatorWithMonad.calculatePrice("xxx","iii"))
    println(PriceCalculatorWithMonad.calculatePriceWithLog("xxx","iii"))

  }

  /**
   * 创建一个Monad的诀窍：
   * 1. 定义包含flatMap和map的接口。
   * 2. 选取一种方式来获取monad的值(模式匹配或apply)。
   * 3. 遵循 monadic laws。
   *
   * 为什么实现Monad就必须实现flatMap 和 map ，实际上
   * map 用于创建一个State Monad实例；
   * flatMap 则用于平面化嵌套的State。
   *
   * 学习Monad的要领是：学会发现Monad并使用，学会发明Monad。
   */
  test("Building blocks for monads"){

    // 不使用for-comprehension的形式
    import StateMonad.State._
    object PriceCalculatorWithMonad2 {

      import Stubs._

      case class PriceState(productId: String, stateCode: String, price: Double)
      def findBasePrice(ps: PriceState): Double = {
        findTheBasePrice(ps.productId)
      }
      def applyStateSpecificDiscount(ps: PriceState): Double = {
        findStateSpecificDiscount(ps.productId, ps.stateCode)
      }
      def applyProductSpecificDiscount(ps: PriceState): Double = {
        ps.price - findProductSpecificDiscount(ps.productId)
      }
      def applyTax(ps: PriceState): Double = {
        ps.price + calculateTax(ps.productId, ps.price)
      }

      def calculatePrice2(productId: String, stateCode: String): Double = {
        def modifyPriceState(f: PriceState => Double) =
          modify[PriceState](s => s.copy(price = f(s)))
        val stateMonad = modifyPriceState(findBasePrice) flatMap { a =>
          modifyPriceState(applyStateSpecificDiscount) flatMap { b =>
            modifyPriceState(applyProductSpecificDiscount) flatMap { c =>
              modifyPriceState(applyTax) map { d => () }
            }
          }
        }
        val initialPriceState = PriceState(productId, stateCode, 0.0)
        val finalPriceState = stateMonad.apply(initialPriceState)._1
        val finalPrice = finalPriceState.price
        finalPrice
      }
    }

    println(PriceCalculatorWithMonad2.calculatePrice2("xxx","iii"))

  }

  /**
   * 总结：
   * 函数式编程、纯函数、递归、尾递归、高阶函数、科里化、偏函数、函数组合、代数数据类型、函数式编程设计模式Monad。
   */
  test("Summary"){}

}







































